/*
 * lexer.c
 *
 *  Created on: Dec 12, 2010
 *      Author: cpy.prefers.you@gmail.com
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "lexer.h"
#include "clogo.h"
#include "buildin_commands.h"
#include "symbol_table.h"
#ifdef OS_WINDOWS
#include <windows.h>
#endif

#define IS_QUOTE(_chr)      (_chr == '"')
#define IS_COLON(_chr)      (_chr == ':')
#define IS_COMMENT(_chr)    (_chr == ';')
#define IS_L_PARENTHESIS(_chr) (_chr == '(')
#define IS_R_PARENTHESIS(_chr) (_chr == ')')
#define IS_OP(_chr)         (\
                              _chr == '+' || _chr == '-' || _chr == '*' || _chr == '/' || \
                              _chr == '`' || _chr == '=' || _chr == '>' || _chr == '<' \
                              )
#define IS_BRACKET(_chr)    (\
                              _chr == '[' || _chr == ']' || _chr == '{' || _chr == '}' || \
                              _chr == '(' || _chr == ')' \
                              )

FILE* TempFile;
static char InputBuffer[INPUT_BUFFER_SIZE] = "CLOGO";
static int UnpairedParenthesisCount = 0;

static void SkipWhite(char **InputPointer);
static int IsNumber(char *Name);
static int IsNumberInWord(char *Name);
static int GetNumber(char **InputPointer, char *Name);
static int ArrayOrListGetWord(char **InputPointer, char *Word);
static int GetWord(char **InputPointer, char *Word);
static int GetDeref(char **InputPointer, char *Deref);
static int GetOp(char **InputPointer, char *Op);
static int GetName(char **InputPointer, char *Name);
static Symbol_T *ArrayOrListScan(char **InputPointer);
static Symbol_T *Scan(char **InputPointer);
static SymbolList_T *ArrayOrListLexicalAnalyze(char **InputPointer, char *EndSign);

static void SkipWhite(char **InputPointer)
{
    while (isspace(**InputPointer))
        (*InputPointer)++;
    return;
}

static int IsNumber(char *Name)
{
    if (!*Name)
        return 0;
    char *tmp;
    strtod(Name, &tmp);
    return tmp > Name && (*tmp == 0 || isspace(*tmp) || IS_OP(*tmp) || IS_BRACKET(*tmp));
}

static int IsNumberInWord(char *Name)
{
    if (!*Name)
        return 0;
    char *tmp;
    strtod(Name + 1, &tmp);
    return *Name == '\"' && tmp > Name + 1 && (*tmp == 0 || isspace(*tmp) || IS_BRACKET(*tmp));
}

static int GetNumber(char **InputPointer, char *Name)
{
    char *tmp;
    *Name = '\"';
    sprintf(Name + 1, "%.*G", NUM_PRINT_LEN, strtod(*InputPointer, &tmp));
    *InputPointer = tmp;
    return TYPE_NUMBER;
}

static int ArrayOrListGetWord(char **InputPointer, char *Word)
{
    *Word = '"';
    Word++;
    while (!(isspace(**InputPointer) || IS_BRACKET(**InputPointer)) && **InputPointer)
    {
        *Word = **InputPointer;
        Word++;
        (*InputPointer)++;
    }
    *Word = 0;
    return TYPE_WORD;
}

static int GetWord(char **InputPointer, char *Word)
{
    while (!(isspace(**InputPointer) || IS_BRACKET(**InputPointer)) && **InputPointer)
    {
        *Word = **InputPointer;
        Word++;
        (*InputPointer)++;
    }
    *Word = 0;
    return TYPE_WORD;
}

static int GetDeref(char **InputPointer, char *Deref)
{
    while (!(isspace(**InputPointer) || IS_BRACKET(**InputPointer)) && **InputPointer)
    {
        *Deref = **InputPointer;
        Deref++;
        (*InputPointer)++;
    }
    *Deref = 0;
    return TYPE_DEREF;
}

static int GetOp(char **InputPointer, char *Op)
{
    char *OpBackup = Op;
    while (IS_OP(**InputPointer))
    {
        *Op = **InputPointer;
        *(Op + 1) = 0;
        if (!LookupName(OpBackup))
            break;
        (*InputPointer)++;
        Op++;
    }
    *Op = 0;
    return TYPE_UNKNOWN;
}

static int GetName(char **InputPointer, char *Name)
{
    while (isgraph(**InputPointer) && !IS_BRACKET(**InputPointer))
    {
        *Name = **InputPointer;
        Name++;
        (*InputPointer)++;
    }
    *Name = 0;
    return TYPE_NAME;
}

void SetNameForArrayOrList(Symbol_T *Symbol, char *Name)
{
    SymbolList_T *SymbolList = NewSymbolList();
    SymbolList_T *SymbolListBackup = SymbolList;
    SymbolList->Symbol = Symbol;
    SymbolList->Next = NULL;
    int PrintLNBackup = PrintLN;
    FILE *OutputStreamBackup = OutputStream;
    PrintLN = 1;
    rewind(TempFile);
    OutputStream = TempFile;
    _show(&SymbolList);
    PrintLN = PrintLNBackup;
    OutputStream = OutputStreamBackup;
    rewind(TempFile);
    fgets(Name, TOKEN_LEN, TempFile);
    free(SymbolListBackup);
    return;
}

static Symbol_T *ArrayOrListScan(char **InputPointer)
{
    SkipWhite(InputPointer);

    Symbol_T *Symbol = NewSymbol();

    if (IS_BRACKET(**InputPointer))
    {
        switch(**InputPointer)
        {
            case '[':
                (*InputPointer)++;
                SkipWhite(InputPointer);
                Symbol->Element = ArrayOrListLexicalAnalyze(InputPointer, "]");
                Symbol->Type = TYPE_LIST;
                SetNameForArrayOrList(Symbol, Symbol->Name);
                break;
            case '{':
                (*InputPointer)++;
                SkipWhite(InputPointer);
                Symbol->Element = ArrayOrListLexicalAnalyze(InputPointer, "}");
                Symbol->Type = TYPE_ARRAY;
                SetNameForArrayOrList(Symbol, Symbol->Name);
                break;
            default:
                if (IS_L_PARENTHESIS(**InputPointer))
                    UnpairedParenthesisCount++;
                else if (IS_R_PARENTHESIS(**InputPointer) && UnpairedParenthesisCount)
                    UnpairedParenthesisCount--;
                Symbol->Name[0] = **InputPointer;
                Symbol->Name[1] = 0;
                Symbol->Type = TYPE_UNKNOWN;
                (*InputPointer)++;
                SkipWhite(InputPointer);
                break;
        }
    }
    else if (IsNumber(*InputPointer))
        Symbol->Type = GetNumber(InputPointer, Symbol->Name);
    else
        Symbol->Type = ArrayOrListGetWord(InputPointer, Symbol->Name);

    Symbol_T *TempSymbol = LookupName(Symbol->Name);
    if (TempSymbol)
        CopySymbol(&Symbol, TempSymbol);

    return Symbol;
}

static Symbol_T *Scan(char **InputPointer)
{
    SkipWhite(InputPointer);
    Symbol_T *Symbol = NewSymbol();
    if (IS_BRACKET(**InputPointer))
    {
        switch(**InputPointer)
        {
            case '[':
                (*InputPointer)++;
                SkipWhite(InputPointer);
                Symbol->Element = ArrayOrListLexicalAnalyze(InputPointer, "]");
                Symbol->Type = TYPE_LIST;
                SetNameForArrayOrList(Symbol, Symbol->Name);
                break;
            case '{':
                (*InputPointer)++;
                SkipWhite(InputPointer);
                Symbol->Element = ArrayOrListLexicalAnalyze(InputPointer, "}");
                Symbol->Type = TYPE_ARRAY;
                SetNameForArrayOrList(Symbol, Symbol->Name);
                break;
            default:
                if (IS_L_PARENTHESIS(**InputPointer))
                    UnpairedParenthesisCount++;
                else if (IS_R_PARENTHESIS(**InputPointer) && UnpairedParenthesisCount)
                    UnpairedParenthesisCount--;
                Symbol->Name[0] = **InputPointer;
                Symbol->Name[1] = 0;
                Symbol->Type = TYPE_UNKNOWN;
                (*InputPointer)++;
                SkipWhite(InputPointer);
                break;
        }
    }
    else if (IS_OP(**InputPointer))
        Symbol->Type = GetOp(InputPointer, Symbol->Name);
    else if (IsNumberInWord(*InputPointer))
    {
        (*InputPointer)++;
        Symbol->Type = GetNumber(InputPointer, Symbol->Name);
    }
    else if (IsNumber(*InputPointer))
        Symbol->Type = GetNumber(InputPointer, Symbol->Name);
    else if (IS_QUOTE(**InputPointer))
        Symbol->Type = GetWord(InputPointer, Symbol->Name);
    else if (IS_COLON(**InputPointer))
        Symbol->Type = GetDeref(InputPointer, Symbol->Name);
    else
        Symbol->Type = GetName(InputPointer, Symbol->Name);

    Symbol_T *TempSymbol = LookupName(Symbol->Name);
    if (TempSymbol)
        CopySymbol(&Symbol, TempSymbol);

    return Symbol;
}

void LexerInit(FILE *InputStream, char **InputPointer, char *Prompt)
{
    fprintf(stdout, "%s", Prompt);
    strcpy(InputBuffer, "bye");
    fgets(InputBuffer, INPUT_BUFFER_SIZE, InputStream);
    *InputPointer = InputBuffer;
    SkipWhite(InputPointer);
    return;
}

static int EndSignMeet(char **InputPointer, char *EndSign)
{
    if (*EndSign)
        return !strncmp(*InputPointer, EndSign, strlen(EndSign));
    else
        return **InputPointer == *EndSign;
}

static SymbolList_T *ArrayOrListLexicalAnalyze(char **InputPointer, char *EndSign)
{
    SymbolList_T *Token = NewSymbolList();
    SymbolList_T *TokenBackup = Token;

    while (!EndSignMeet(InputPointer, EndSign) || UnpairedParenthesisCount)
    {
        while (!**InputPointer)
            LexerInit(InputStream, InputPointer, SecondaryPrompt);
        if (EndSignMeet(InputPointer, EndSign))
            break;

        if (IS_COMMENT(**InputPointer))
        {
            while (**InputPointer && **InputPointer != LF)
                (*InputPointer)++;
            SkipWhite(InputPointer);
            if(!**InputPointer)
                continue;
        }

        Token->Next = NewSymbolList();
        Token = Token->Next;
        Token->Symbol = ArrayOrListScan(InputPointer);
        SkipWhite(InputPointer);
    }
    if (*EndSign)
        (*InputPointer) += strlen(EndSign);
    else
        (*InputPointer)++;

    Token = TokenBackup->Next;
    free(TokenBackup);

    return Token;
}

SymbolList_T *LexicalAnalyze(char **InputPointer, char *EndSign)
{
    SymbolList_T *Token = NewSymbolList();
    SymbolList_T *TokenBackup = Token;

    while (!EndSignMeet(InputPointer, EndSign) || UnpairedParenthesisCount)
    {
        while (!**InputPointer)
            LexerInit(InputStream, InputPointer, UnpairedParenthesisCount ? SecondaryPrompt : PrimaryPrompt);
        if (EndSignMeet(InputPointer, EndSign))
            break;

        if (IS_COMMENT(**InputPointer))
        {
            while (**InputPointer && **InputPointer != LF)
                (*InputPointer)++;
            SkipWhite(InputPointer);
            if(!**InputPointer)
                continue;
        }

        Token->Next = NewSymbolList();
        Token = Token->Next;
        Token->Symbol = Scan(InputPointer);
        SkipWhite(InputPointer);
    }

    if (*EndSign)
        (*InputPointer) += strlen(EndSign);
    else
        (*InputPointer)++;

    Token = TokenBackup->Next;
    free(TokenBackup);

    return Token;
}
